#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <vector>
#include <map>
#include <arpa/inet.h>
#include "hub-server.hpp"
#include "main.hpp"
#include "utils.hpp"

sockaddr_storage xlln_sockaddr_base_0;
static int xlln_socket_base_0 = 0;

static sem_t xlln_sem_packet_consume;
pthread_mutex_t xlln_mutex_packet_consume;

std::vector<REMOTE_SOCKET_INFO*> xlln_remote_socket_info;
// Key: instanceId.
static std::map<uint32_t, REMOTE_SOCKET_INFO*> xlln_instance_to_remote_socket_info;
static std::vector<std::pair<REMOTE_SOCKET_INFO*, std::pair<uint32_t, uint8_t*>>> xlln_recv_packets;

static pthread_t xlln_thread_port_listener_base_0;
static pthread_t xlln_thread_timeout_cleanup;
static const uint8_t xlln_thread_count_consume_received = 1;
static pthread_t xlln_thread_consume_received[xlln_thread_count_consume_received];

static void* ThreadTimeoutCleanup(void *arg)
{
	struct timespec timeoutTime;
	while (1) {
		clock_gettime(CLOCK_REALTIME, &timeoutTime);
		timeoutTime.tv_sec += 15;
		int resultTimedLock = pthread_mutex_timedlock(&xlln_mutex_exit_threads, &timeoutTime);
		if (resultTimedLock != ETIMEDOUT) {
			if (resultTimedLock == 0) {
				pthread_mutex_unlock(&xlln_mutex_exit_threads);
			}
			XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_DEBUG | XLLN_LOG_LEVEL_INFO
				, "Exiting %s."
				, __func__
			);
			break;
		}
		
		pthread_mutex_lock(&xlln_mutex_packet_consume);
		
		for (uint32_t i = 0; i < xlln_remote_socket_info.size(); i++) {
			REMOTE_SOCKET_INFO* &remoteSocketInfo = xlln_remote_socket_info.at(i);
			if (remoteSocketInfo->lastComm + 60 * 2 > time(NULL)) {
				continue;
			}
			bool isQueued = false;
			for (auto const &recvPacket : xlln_recv_packets) {
				if (recvPacket.first == remoteSocketInfo) {
					isQueued = true;
					break;
				}
			}
			if (isQueued) {
				continue;
			}
			
			char *sockAddrInfo = GET_SOCKADDR_INFO(&remoteSocketInfo->sockaddr);
			XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_DEBUG
				, "Removing remote socket %s with instanceId 0x%08x."
				, sockAddrInfo ? sockAddrInfo : ""
				, remoteSocketInfo->instanceId
			);
			if (sockAddrInfo) {
				free(sockAddrInfo);
			}
			
			xlln_instance_to_remote_socket_info.erase(remoteSocketInfo->instanceId);
			
			if (remoteSocketInfo->portInternalOffsetToExternalAddr) {
				delete remoteSocketInfo->portInternalOffsetToExternalAddr;
			}
			if (remoteSocketInfo->portInternalOriginalToExternalAddr) {
				delete remoteSocketInfo->portInternalOriginalToExternalAddr;
			}
			
			delete remoteSocketInfo;
			xlln_remote_socket_info.erase(xlln_remote_socket_info.begin() + i);
			i--;
		}
		
		pthread_mutex_unlock(&xlln_mutex_packet_consume);
	}
	
	return 0;
}

static void* ThreadConsumeReceived(void *arg)
{
	const int packetSizeHeaderType = sizeof(XLLNNetPacketType::TYPE);
	const int packetSizeHubRequest = sizeof(XLLNNetPacketType::HUB_REQUEST_PACKET);
	const int packetSizeHubReply = sizeof(XLLNNetPacketType::HUB_REPLY_PACKET);
	const int packetSizeHubOutOfBand = sizeof(XLLNNetPacketType::HUB_OUT_OF_BAND);
	const int packetSizePacketForwarded = sizeof(XLLNNetPacketType::PACKET_FORWARDED);
	const int packetSizeHubRelay = sizeof(XLLNNetPacketType::HUB_RELAY);
	
	REMOTE_SOCKET_INFO remoteSocketInfo;
	uint8_t* recvFromBuffer;
	uint32_t recvFromBufferLen;
	while (1) {
		int resultSemWait = sem_wait(&xlln_sem_packet_consume);
		if (resultSemWait) {
			error_t errorSemWait = errno;
			XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_ERROR
				, "sem_wait failed with error %d."
				, errorSemWait
			);
			continue;
		}
		
		{
			// Limit this variable's access to this scope only. Make sure source REMOTE_SOCKET_INFO stays within mutex.
			std::pair<REMOTE_SOCKET_INFO*, std::pair<uint32_t, uint8_t*>> recvPacket;
			bool exit;
			{
				pthread_mutex_lock(&xlln_mutex_packet_consume);
				
				exit = xlln_recv_packets.size() <= 0;
				if (!exit) {
					recvPacket = xlln_recv_packets.at(0);
					xlln_recv_packets.erase(xlln_recv_packets.begin());
					memcpy(&remoteSocketInfo, recvPacket.first, sizeof(REMOTE_SOCKET_INFO));
				}
				
				pthread_mutex_unlock(&xlln_mutex_packet_consume);
			}
			if (exit) {
				break;
			}
			
			recvFromBufferLen = recvPacket.second.first;
			recvFromBuffer = recvPacket.second.second;
		}
		
		char *sockAddrInfo = GET_SOCKADDR_INFO(&remoteSocketInfo.sockaddr);
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_DEBUG
			, "Consuming %s length:%d."
			, sockAddrInfo ? sockAddrInfo : ""
			, recvFromBufferLen
		);
		if (sockAddrInfo) {
			free(sockAddrInfo);
		}
		
		if (recvFromBufferLen >= packetSizeHeaderType) {
			XLLNNetPacketType::TYPE &packetType = *(XLLNNetPacketType::TYPE*)recvFromBuffer;
			switch (packetType) {
				case XLLNNetPacketType::tLIVE_OVER_LAN_ADVERTISE:
				case XLLNNetPacketType::tLIVE_OVER_LAN_UNADVERTISE:
				case XLLNNetPacketType::tTITLE_BROADCAST_PACKET: {
					// Take broadcasted packets and forwarded it to all Core Socket listeners of this Hub server with the same titleId.
					std::vector<sockaddr_storage> broadcastToAddrs;
					
					// Only forward it if the origin is recognised as an XLLN instance and from the Core Socket.
					if (remoteSocketInfo.isCoreSocket == IS_CORE_SOCKET::Type::YES && remoteSocketInfo.instanceId) {
						pthread_mutex_lock(&xlln_mutex_packet_consume);
						
						for (uint32_t i = 0; i < xlln_remote_socket_info.size(); i++) {
							REMOTE_SOCKET_INFO* &savedRemoteSocket = xlln_remote_socket_info.at(i);
							if (
								savedRemoteSocket->isCoreSocket == IS_CORE_SOCKET::Type::YES
								&& savedRemoteSocket->instanceId != remoteSocketInfo.instanceId
								&& savedRemoteSocket->titleId == remoteSocketInfo.titleId
							) {
								broadcastToAddrs.push_back(savedRemoteSocket->sockaddr);
							}
						}
						
						pthread_mutex_unlock(&xlln_mutex_packet_consume);
					}
					
					if (broadcastToAddrs.size()) {
						const int altBufHeaderLen = packetSizeHeaderType + packetSizePacketForwarded;
						const int packetSizeForwarded = altBufHeaderLen + recvFromBufferLen;
						uint8_t *packetBuffer = new uint8_t[packetSizeForwarded];
						packetBuffer[0] = XLLNNetPacketType::tPACKET_FORWARDED;
						XLLNNetPacketType::PACKET_FORWARDED &packetForwardSocketData = *(XLLNNetPacketType::PACKET_FORWARDED*)&packetBuffer[packetSizeHeaderType];
						memcpy(&packetForwardSocketData.originSockAddr, &remoteSocketInfo.sockaddr, sizeof(sockaddr_storage));
						memset(&packetForwardSocketData.netter, 0, sizeof(XLLNNetPacketType::NET_USER_PACKET));
						// Append the original packet to the new forwarding packet.
						memcpy(packetBuffer + altBufHeaderLen, recvFromBuffer, recvFromBufferLen);
						
						packetForwardSocketData.netter.instanceId = remoteSocketInfo.instanceId;
						packetForwardSocketData.netter.portBaseHBO = remoteSocketInfo.portBaseHBO;
						
						for (sockaddr_storage &broadcastToAddr : broadcastToAddrs) {
							{
								char *sockAddrInfo = GET_SOCKADDR_INFO(&broadcastToAddr);
								XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_DEBUG
									, "%s sendto %s length:%d."
									, XLLNNetPacketType::TYPE_NAMES[packetType]
									, sockAddrInfo ? sockAddrInfo : ""
									, packetSizeForwarded
								);
								if (sockAddrInfo) {
									free(sockAddrInfo);
								}
							}
							
							sendto(xlln_socket_base_0, packetBuffer, packetSizeForwarded, 0, (const sockaddr*)&broadcastToAddr, sizeof(broadcastToAddr));
						}
						
						delete[] packetBuffer;
					}
					
					break;
				}
				case XLLNNetPacketType::tHUB_REQUEST: {
					XLLNNetPacketType::HUB_REQUEST_PACKET *requestPacket = (XLLNNetPacketType::HUB_REQUEST_PACKET*)&recvFromBuffer[packetSizeHeaderType];
					if (recvFromBufferLen < packetSizeHeaderType + packetSizeHubRequest) {
						break;
					}
					
					const int altBufferSize = packetSizeHeaderType + packetSizeHubReply;
					uint8_t *altBuf = new uint8_t[altBufferSize];
					altBuf[0] = XLLNNetPacketType::tHUB_REPLY;
					XLLNNetPacketType::HUB_REPLY_PACKET *replyPacket = (XLLNNetPacketType::HUB_REPLY_PACKET*)&altBuf[packetSizeHeaderType];
					const uint32_t hubVersion = (VERSION_MAJOR << 24) + (VERSION_MINOR << 16) + (VERSION_REVISION << 8) + VERSION_BUILD;
					replyPacket->isHubServer = requestPacket->xllnVersion < (hubVersion & 0xFFFF0000) ? false : true;
					replyPacket->xllnVersion = hubVersion;
					// TODO no recommendations yet, just storing it atm with no checking.
					replyPacket->recommendedInstanceId = 0;
					
					{
						char *sockAddrInfo = GET_SOCKADDR_INFO(&remoteSocketInfo.sockaddr);
						XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_DEBUG
							, "%s sendto %s length:%d."
							, XLLNNetPacketType::TYPE_NAMES[packetType]
							, sockAddrInfo ? sockAddrInfo : ""
							, altBufferSize
						);
						if (sockAddrInfo) {
							free(sockAddrInfo);
						}
					}
					
					sendto(xlln_socket_base_0, altBuf, altBufferSize, 0, (sockaddr*)&remoteSocketInfo.sockaddr, sizeof(sockaddr_storage));
					
					delete[] altBuf;
					
					{
						pthread_mutex_lock(&xlln_mutex_packet_consume);
						
						for (uint32_t iRemoteSocket = 0; iRemoteSocket < xlln_remote_socket_info.size(); iRemoteSocket++) {
							REMOTE_SOCKET_INFO* &savedRemoteSocket = xlln_remote_socket_info.at(iRemoteSocket);
							if (SockAddrsMatch(&savedRemoteSocket->sockaddr, &remoteSocketInfo.sockaddr)) {
								savedRemoteSocket->instanceId = requestPacket->instanceId;
								savedRemoteSocket->isCoreSocket = IS_CORE_SOCKET::Type::YES;
								savedRemoteSocket->xllnVersion = requestPacket->xllnVersion;
								savedRemoteSocket->titleId = requestPacket->titleId;
								savedRemoteSocket->titleVersion = requestPacket->titleVersion;
								savedRemoteSocket->portBaseHBO = requestPacket->portBaseHBO;
								
								if (requestPacket->instanceId) {
									xlln_instance_to_remote_socket_info[requestPacket->instanceId] = savedRemoteSocket;
								}
								
								break;
							}
						}
						
						pthread_mutex_unlock(&xlln_mutex_packet_consume);
					}
					
					break;
				}
				case XLLNNetPacketType::tHUB_OUT_OF_BAND: {
					if (recvFromBufferLen < packetSizeHeaderType + packetSizeHubOutOfBand + packetSizeHeaderType) {
						break;
					}
					
					uint8_t *packetBroadcastPacket = &recvFromBuffer[packetSizeHeaderType + packetSizeHubOutOfBand];
					uint32_t packetSizeBroadcastPacket = recvFromBufferLen - (packetSizeHeaderType + packetSizeHubOutOfBand);
					if (packetBroadcastPacket[0] != XLLNNetPacketType::tTITLE_BROADCAST_PACKET) {
						break;
					}
					
					XLLNNetPacketType::HUB_OUT_OF_BAND *packetOutOfBand = (XLLNNetPacketType::HUB_OUT_OF_BAND*)&recvFromBuffer[packetSizeHeaderType];
					
					if (!packetOutOfBand->instanceId) {
						break;
					}
					if (packetOutOfBand->portOffsetHBO >= 100 && packetOutOfBand->portOffsetHBO != 0xFF) {
						break;
					}
					if (packetOutOfBand->portOffsetHBO == 0xFF && !packetOutOfBand->portOriginalHBO) {
						break;
					}
					
					std::vector<sockaddr_storage> broadcastToAddrs;
					
					{
						pthread_mutex_lock(&xlln_mutex_packet_consume);
						
						if (xlln_instance_to_remote_socket_info.count(packetOutOfBand->instanceId)) {
							REMOTE_SOCKET_INFO *socketRemoteCore = xlln_instance_to_remote_socket_info[packetOutOfBand->instanceId];
							
							remoteSocketInfo.instanceId = socketRemoteCore->instanceId;
							remoteSocketInfo.portBaseHBO = socketRemoteCore->portBaseHBO;
							
							for (uint32_t iRemoteSocket = 0; iRemoteSocket < xlln_remote_socket_info.size(); iRemoteSocket++) {
								REMOTE_SOCKET_INFO* &savedRemoteSocket = xlln_remote_socket_info.at(iRemoteSocket);
								if (SockAddrsMatch(&savedRemoteSocket->sockaddr, &remoteSocketInfo.sockaddr)) {
									savedRemoteSocket->isCoreSocket = IS_CORE_SOCKET::Type::NO;
									savedRemoteSocket->instanceId = socketRemoteCore->instanceId;
									savedRemoteSocket->portBaseHBO = socketRemoteCore->portBaseHBO;
									
									if (socketRemoteCore->lastComm < savedRemoteSocket->lastComm) {
										socketRemoteCore->lastComm = savedRemoteSocket->lastComm;
									}
									
									break;
								}
							}
							
							if (IsUsingBasePort(socketRemoteCore->portBaseHBO)) {
								if (packetOutOfBand->portOffsetHBO != 0xFF) {
									if (!socketRemoteCore->portInternalOffsetToExternalAddr) {
										socketRemoteCore->portInternalOffsetToExternalAddr = new std::map<uint8_t, sockaddr_storage>();
									}
									
									(*socketRemoteCore->portInternalOffsetToExternalAddr)[packetOutOfBand->portOffsetHBO] = remoteSocketInfo.sockaddr;
								}
							}
							else {
								if (packetOutOfBand->portOriginalHBO) {
									if (!socketRemoteCore->portInternalOriginalToExternalAddr) {
										socketRemoteCore->portInternalOriginalToExternalAddr = new std::map<uint16_t, sockaddr_storage>();
									}
									
									(*socketRemoteCore->portInternalOriginalToExternalAddr)[packetOutOfBand->portOriginalHBO] = remoteSocketInfo.sockaddr;
								}
							}
							
							for (uint32_t iRemoteSocket = 0; iRemoteSocket < xlln_remote_socket_info.size(); iRemoteSocket++) {
								REMOTE_SOCKET_INFO* &savedRemoteSocket = xlln_remote_socket_info.at(iRemoteSocket);
								
								if (
									savedRemoteSocket->isCoreSocket == IS_CORE_SOCKET::Type::YES
									&& savedRemoteSocket->instanceId != socketRemoteCore->instanceId
									&& savedRemoteSocket->titleId == socketRemoteCore->titleId
								) {
									sockaddr_storage broadcastToAddr = savedRemoteSocket->sockaddr;
									
									if (IsUsingBasePort(savedRemoteSocket->portBaseHBO)) {
										if (packetOutOfBand->portOffsetHBO != 0xFF) {
											if (savedRemoteSocket->portInternalOffsetToExternalAddr && (*savedRemoteSocket->portInternalOffsetToExternalAddr).count(packetOutOfBand->portOffsetHBO)) {
												broadcastToAddr = (*savedRemoteSocket->portInternalOffsetToExternalAddr)[packetOutOfBand->portOffsetHBO];
											}
											else {
												SetSockAddrPort(&broadcastToAddr, savedRemoteSocket->portBaseHBO + packetOutOfBand->portOffsetHBO);
											}
											
											broadcastToAddrs.push_back(broadcastToAddr);
										}
									}
									else {
										if (packetOutOfBand->portOriginalHBO) {
											if (savedRemoteSocket->portInternalOriginalToExternalAddr && (*savedRemoteSocket->portInternalOriginalToExternalAddr).count(packetOutOfBand->portOriginalHBO)) {
												broadcastToAddr = (*savedRemoteSocket->portInternalOriginalToExternalAddr)[packetOutOfBand->portOriginalHBO];
											}
											else {
												SetSockAddrPort(&broadcastToAddr, packetOutOfBand->portOriginalHBO);
											}
											
											broadcastToAddrs.push_back(broadcastToAddr);
										}
									}
								}
							}
						}
						
						pthread_mutex_unlock(&xlln_mutex_packet_consume);
					}
					
					if (broadcastToAddrs.size()) {
						const int packetSize = packetSizeHeaderType + packetSizePacketForwarded + packetSizeBroadcastPacket;
						
						uint8_t *packetBuffer = new uint8_t[packetSize];
						packetBuffer[0] = XLLNNetPacketType::tPACKET_FORWARDED;
						XLLNNetPacketType::PACKET_FORWARDED &packetPacketForwarded = *(XLLNNetPacketType::PACKET_FORWARDED*)&packetBuffer[packetSizeHeaderType];
						
						packetPacketForwarded.originSockAddr = remoteSocketInfo.sockaddr;
						
						memset(&packetPacketForwarded.netter, 0, sizeof(XLLNNetPacketType::NET_USER_PACKET));
						packetPacketForwarded.netter.instanceId = remoteSocketInfo.instanceId;
						packetPacketForwarded.netter.portBaseHBO = remoteSocketInfo.portBaseHBO;
						
						// Append the original Broadcast packet to the new forwarding packet.
						memcpy(&packetBuffer[packetSizeHeaderType + packetSizePacketForwarded], packetBroadcastPacket, packetSizeBroadcastPacket);
						
						for (sockaddr_storage &broadcastToAddr : broadcastToAddrs) {
							{
								char *sockAddrInfo = GET_SOCKADDR_INFO(&broadcastToAddr);
								XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_DEBUG
									, "%s sendto %s length:%d."
									, XLLNNetPacketType::TYPE_NAMES[packetType]
									, sockAddrInfo ? sockAddrInfo : ""
									, packetSizeBroadcastPacket
								);
								if (sockAddrInfo) {
									free(sockAddrInfo);
								}
							}
							
							sendto(xlln_socket_base_0, packetBuffer, packetSize, 0, (const sockaddr*)&broadcastToAddr, sizeof(broadcastToAddr));
						}
						
						delete[] packetBuffer;
					}
					
					break;
				}
				case XLLNNetPacketType::tHUB_RELAY: {
					if (recvFromBufferLen <= packetSizeHeaderType + packetSizeHubRelay) {
						break;
					}
					if (!remoteSocketInfo.instanceId) {
						break;
					}
					
					int packetSizeOriginal = recvFromBufferLen - (packetSizeHeaderType + packetSizeHubRelay);
					uint8_t *packetDataOriginal = &recvFromBuffer[packetSizeHeaderType + packetSizeHubRelay];
					XLLNNetPacketType::HUB_RELAY *packetHubRelay = (XLLNNetPacketType::HUB_RELAY*)&recvFromBuffer[packetSizeHeaderType];
					
					{
						const int packetSize = packetSizeHeaderType + packetSizePacketForwarded + packetSizeOriginal;
						
						uint8_t *packetBuffer = new uint8_t[packetSize];
						packetBuffer[0] = XLLNNetPacketType::tPACKET_FORWARDED;
						XLLNNetPacketType::PACKET_FORWARDED &packetPacketForwarded = *(XLLNNetPacketType::PACKET_FORWARDED*)&packetBuffer[packetSizeHeaderType];
						
						packetPacketForwarded.originSockAddr = remoteSocketInfo.sockaddr;
						
						memset(&packetPacketForwarded.netter, 0, sizeof(XLLNNetPacketType::NET_USER_PACKET));
						packetPacketForwarded.netter = packetHubRelay->netterOrigin;
						packetPacketForwarded.netter.instanceId = remoteSocketInfo.instanceId;
						packetPacketForwarded.netter.portBaseHBO = remoteSocketInfo.portBaseHBO;
						
						// Append the original packet to the new forwarding packet.
						memcpy(&packetBuffer[packetSizeHeaderType + packetSizePacketForwarded], packetDataOriginal, packetSizeOriginal);
						
						{
							char *sockAddrInfo = GET_SOCKADDR_INFO(&packetHubRelay->destSockAddr);
							XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_DEBUG
								, "%s sendto %s original data length:%d."
								, XLLNNetPacketType::TYPE_NAMES[packetType]
								, sockAddrInfo ? sockAddrInfo : ""
								, packetSizeOriginal
							);
							if (sockAddrInfo) {
								free(sockAddrInfo);
							}
						}
						
						sendto(xlln_socket_base_0, packetBuffer, packetSize, 0, (const sockaddr*)&packetHubRelay->destSockAddr, sizeof(packetHubRelay->destSockAddr));
						
						delete[] packetBuffer;
					}
					
					break;
				}
			}
		}
		
		delete[] recvFromBuffer;
	}
	
	XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_INFO
		, "No more to consume. Exiting thread."
	);
	
	return 0;
}

static void* ThreadPortListenerBase0(void *arg)
{
	char buff[1024];
	ssize_t resultRecv;
	sockaddr_storage sockAddrRemote;
	socklen_t sockAddrRemoteLen = sizeof(sockAddrRemote);
	do {
#ifdef __CYGWIN__
		// HACK: Cygwin does not appear to support / work right with MSG_WAITALL.
		resultRecv = recvfrom(xlln_socket_base_0, buff, sizeof(buff), 0, (sockaddr*)&sockAddrRemote, &sockAddrRemoteLen);
#else
		resultRecv = recvfrom(xlln_socket_base_0, buff, sizeof(buff), MSG_WAITALL, (sockaddr*)&sockAddrRemote, &sockAddrRemoteLen);
#endif
		if (resultRecv < 0) {
			int resultErrno = errno;
			XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
				, "recvfrom failed on socket %d with error %d."
				, xlln_socket_base_0
				, resultErrno
			);
			continue;
		}
		else if (resultRecv == 0) {
			int resultExitLock = pthread_mutex_trylock(&xlln_mutex_exit_threads);
			if (resultExitLock == 0) {
				pthread_mutex_unlock(&xlln_mutex_exit_threads);
				break;
			}
		}
		else if (resultRecv > 1024) {
			int resultErrno = errno;
			XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
				, "recvfrom packet size >1024 of %zu on socket %d with error %d."
				, resultRecv
				, xlln_socket_base_0
				, resultErrno
			);
			continue;
		}
		
		REMOTE_SOCKET_INFO *createRemoteSocketInfo = 0;
		if (!createRemoteSocketInfo) {
			createRemoteSocketInfo = new REMOTE_SOCKET_INFO;
			memcpy(&createRemoteSocketInfo->sockaddr, &sockAddrRemote, sizeof(sockaddr_storage));
		}
		
		char *sockAddrInfo = GET_SOCKADDR_INFO(&sockAddrRemote);
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_DEBUG
			, "recvfrom %s length:%zu."
			, sockAddrInfo ? sockAddrInfo : ""
			, resultRecv
		);
		if (sockAddrInfo) {
			free(sockAddrInfo);
		}
		
		uint8_t *bufferCopy = new uint8_t[resultRecv];
		memcpy(bufferCopy, buff, resultRecv);
		
		{
			pthread_mutex_lock(&xlln_mutex_packet_consume);
			
			REMOTE_SOCKET_INFO *foundRemoteSocketInfo = 0;
			
			for (uint32_t iRemoteSocket = 0; iRemoteSocket < xlln_remote_socket_info.size(); iRemoteSocket++) {
				REMOTE_SOCKET_INFO* &savedRemoteSocket = xlln_remote_socket_info.at(iRemoteSocket);
				if (SockAddrsMatch(&savedRemoteSocket->sockaddr, &createRemoteSocketInfo->sockaddr)) {
					foundRemoteSocketInfo = savedRemoteSocket;
					break;
				}
			}
			
			if (!foundRemoteSocketInfo) {
				foundRemoteSocketInfo = createRemoteSocketInfo;
				createRemoteSocketInfo = 0;
				xlln_remote_socket_info.push_back(foundRemoteSocketInfo);
			}
			
			foundRemoteSocketInfo->lastComm = time(NULL);
			
			std::pair<REMOTE_SOCKET_INFO*, std::pair<uint32_t, uint8_t*>> recvPacket = std::make_pair(foundRemoteSocketInfo, std::make_pair(resultRecv, bufferCopy));
			
			xlln_recv_packets.push_back(recvPacket);
			
			pthread_mutex_unlock(&xlln_mutex_packet_consume);
		}
		
		sem_post(&xlln_sem_packet_consume);
		
		if (createRemoteSocketInfo) {
			delete createRemoteSocketInfo;
		}
	} while (resultRecv >= 0);
	
	return 0;
}

int InitHubServer()
{
	int result = EXIT_SUCCESS;
	
	if (result = pthread_mutex_init(&xlln_mutex_packet_consume, NULL)) {
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "pthread_mutex_init failed on xlln_mutex_packet_consume with error %d."
			, result
		);
		return result;
	}
	
	if (result = sem_init(&xlln_sem_packet_consume, 0, 0)) {
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "sem_init failed on xlln_sem_packet_consume with error %d."
			, result
		);
		return result;
	}
	
	xlln_socket_base_0 = socket(xlln_sockaddr_base_0.ss_family, SOCK_DGRAM, IPPROTO_UDP);
	if (xlln_socket_base_0 < 0) {
		result = errno;
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "create socket failed with error %d."
			, result
		);
		return result;
	}
	
	// Allow address (port number) to be reused immediately.
	int optVal = 1;
	if (setsockopt(xlln_socket_base_0, SOL_SOCKET, SO_REUSEADDR, &optVal, sizeof(int)) < 0) {
		result = errno;
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "setsockopt failed on socket %d with error %d."
			, xlln_socket_base_0
			, result
		);
		return result;
	}
	
	{
		char *sockAddrInfo = GetSockAddrInfo(&xlln_sockaddr_base_0);
		printf("Hosting on: %s.\n", sockAddrInfo);
		free(sockAddrInfo);
	}
	
	{
		char *sockAddrInfo = GET_SOCKADDR_INFO(&xlln_sockaddr_base_0);
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_INFO
			, "Binding to socket %d on %s."
			, xlln_socket_base_0
			, sockAddrInfo ? sockAddrInfo : ""
		);
		if (sockAddrInfo) {
			free(sockAddrInfo);
		}
	}
	
	if (bind(xlln_socket_base_0, (struct sockaddr *)&xlln_sockaddr_base_0, sizeof(xlln_sockaddr_base_0)) < 0) {
		result = errno;
		char *sockAddrInfo = GET_SOCKADDR_INFO(&xlln_sockaddr_base_0);
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "Failed to bind socket %d to %s with error %d."
			, xlln_socket_base_0
			, sockAddrInfo ? sockAddrInfo : ""
			, result
		);
		if (sockAddrInfo) {
			free(sockAddrInfo);
		}
		return result;
	}
	
	pthread_attr_t thread_attr;
	pthread_attr_init(&thread_attr);
	pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_JOINABLE);
	
	pthread_create(&xlln_thread_port_listener_base_0, &thread_attr, ThreadPortListenerBase0, NULL);
	pthread_create(&xlln_thread_timeout_cleanup, &thread_attr, ThreadTimeoutCleanup, NULL);
	
	for (uint8_t i = 0; i < xlln_thread_count_consume_received; i++) {
		pthread_create(&xlln_thread_consume_received[i], &thread_attr, ThreadConsumeReceived, NULL);
	}
	
	pthread_attr_destroy(&thread_attr);
	
	return result;
}

int UninitHubServer()
{
	int result = EXIT_SUCCESS;
	void *resultThread = 0;
	
	if (shutdown(xlln_socket_base_0, SHUT_RD) < 0) {
		result = errno;
		if (result != ENOTCONN) {
			XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
				, "shutdown socket %d failed with error %d."
				, xlln_socket_base_0
				, result
			);
			return result;
		}
	}
	
	if (result = pthread_join(xlln_thread_port_listener_base_0, &resultThread)) {
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "pthread_join failed on xlln_thread_port_listener_base_0=%d with error %d."
			, xlln_thread_port_listener_base_0
			, result
		);
		return result;
	}
	
	if (result = pthread_join(xlln_thread_timeout_cleanup, &resultThread)) {
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "pthread_join failed on xlln_thread_timeout_cleanup=%d with error %d."
			, xlln_thread_timeout_cleanup
			, result
		);
		return result;
	}
	
	for (uint8_t i = 0; i < xlln_thread_count_consume_received; i++) {
		sem_post(&xlln_sem_packet_consume);
	}
	
	for (uint8_t i = 0; i < xlln_thread_count_consume_received; i++) {
		if (result = pthread_join(xlln_thread_consume_received[i], &resultThread)) {
			XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
				, "pthread_join failed on xlln_thread_consume_received[%hhd]=%d with error %d."
				, xlln_thread_consume_received[i]
				, i
				, result
			);
			return result;
		}
	}
	
	{
		pthread_mutex_lock(&xlln_mutex_packet_consume);
		
		xlln_instance_to_remote_socket_info.clear();
		
		for (uint32_t i = 0; i < xlln_remote_socket_info.size(); i++) {
			REMOTE_SOCKET_INFO* &remoteSocketInfo = xlln_remote_socket_info.at(i);
			
			if (remoteSocketInfo->portInternalOffsetToExternalAddr) {
				delete remoteSocketInfo->portInternalOffsetToExternalAddr;
			}
			if (remoteSocketInfo->portInternalOriginalToExternalAddr) {
				delete remoteSocketInfo->portInternalOriginalToExternalAddr;
			}
			
			delete remoteSocketInfo;
		}
		xlln_remote_socket_info.clear();
		
		pthread_mutex_unlock(&xlln_mutex_packet_consume);
	}
	
	if (close(xlln_socket_base_0) < 0) {
		result = errno;
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "close socket %d failed with error %d."
			, xlln_socket_base_0
			, result
		);
		return result;
	}
	xlln_socket_base_0 = 0;
	
	if (result = sem_destroy(&xlln_sem_packet_consume)) {
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "sem_destroy failed on xlln_sem_packet_consume with error %d."
			, result
		);
		return result;
	}
	
	if (result = pthread_mutex_destroy(&xlln_mutex_packet_consume)) {
		XLLN_DEBUG_LOG(XLLN_LOG_CONTEXT_XLIVELESSNESS | XLLN_LOG_LEVEL_FATAL
			, "pthread_mutex_destroy failed on xlln_mutex_packet_consume with error %d."
			, result
		);
		return result;
	}
	
	return result;
}